package Optimizer;

import Engine.src.ca.uqac.dim.turtledb.*;
import Engine.src.ca.uqac.dim.turtledb.QueryVisitor.VisitorException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class QueryOptimizer {

    private HashSet<String> sites; // PAS DE DOUBLONS AVEC UNE COLLECTION SET
    private HashMap<String, Relation> fragments; // <Nom, Composition>
    private HashMap<String, String> allocation_fragments; // <Fragment, Site>
    private HashMap<String, Integer> couts_stockage; // <Site, Cout>
    private HashMap<String, HashMap<String, Integer>> couts_communication; // <Site 1 <Site 2, Cout>>

    private HashSet<Relation> test;
    
    public QueryOptimizer(){
        sites = new HashSet<String>();
        fragments = new HashMap<String, Relation>();
        allocation_fragments = new HashMap<String, String>();
        couts_stockage = new HashMap<String, Integer>();
        couts_communication = new HashMap<String, HashMap<String,Integer>>();
        
        test = new HashSet<Relation>();     
    }

    public void setSites(HashSet<String> sites){
        this.sites = sites;
    }
    
    public HashSet<String> getSites(){
        return sites;
    }

    public void addSite(String site){
        sites.add(site);
    }

    public void removeSite(String site){
        sites.remove(site);
    }

    public void setFragments(HashMap<String, Relation> fragments){
        this.fragments = fragments;
    }
    
    public HashMap<String, Relation> getFragments(){
        return fragments;
    }

    public void addFragment(String nom, Relation composition){
        fragments.put(nom, composition);
    }

    public void removeFragment(String nom){
        fragments.remove(nom);
    }

    public Relation getCompositionFragment(String nom){
        return fragments.get(nom);
    }

    public String getNomFragment(Relation composition){
        String fragment_name = null;
                    
        for(Map.Entry<String, Relation> e : fragments.entrySet()){
            if(e.getValue().equals(composition)){
                fragment_name = e.getKey();
                break;
            }
        }  
        
        return fragment_name;
    }
    
    public void setAllocationFragmentSite(String fragment, String site){        
        allocation_fragments.put(fragment, site);
    }
    
    public String getAllocationFragmentSite(String fragment){
        return allocation_fragments.get(fragment);
    }

    public void setCoutStockageSite(String site, int cout){
        couts_stockage.put(site, cout);
    }

    public int getCoutStockageSite(String site){
        if(!couts_stockage.containsKey(site))
            return 0;
        
        return couts_stockage.get(site);
    }

    public void setCoutCommunication(String site1, String site2, int cout){
        if(couts_communication.containsKey(site1)){
                couts_communication.get(site1).put(site2, cout);
        }
        else{
                HashMap<String, Integer> h = new HashMap<String, Integer>();
                h.put(site2, cout);
                couts_communication.put(site1, h);
        }

        if(couts_communication.containsKey(site2)){
                couts_communication.get(site2).put(site1, cout);
        }
        else{
                HashMap<String, Integer> h = new HashMap<String, Integer>();
                h.put(site1, cout);
                couts_communication.put(site2, h);
        }
    }

    public int getCoutCommunication(String site1, String site2){        
        if(site1.compareTo(site2) == 0)
            return 0;
        
        if(!couts_communication.containsKey(site1) || !couts_communication.get(site1).containsKey(site2))
            return 0;
        
        return couts_communication.get(site1).get(site2);
    }
    
    public int getCost(QueryPlan p){
        int cout = 0;

        // Pour chaque elt dans le plan de requete
        for(Map.Entry<String, Set<Relation>> entry : p.entrySet()){  
            // Pour chaque fragments d'un elt 
           
            for(Relation r : entry.getValue()){
                   
                String site_fragment = getAllocationFragmentSite(getNomFragment(r));
                
                cout += r.tupleCount()*getCoutStockageSite(site_fragment);
                cout += r.tupleCount()*getCoutCommunication(site_fragment, entry.getKey());      
            }            
        }
        
        return cout;
    }

    

    public QueryPlan optimizeQuery(Relation q){
        QueryPlan qp = null;
        
        QueryVisitor v = new QueryVisitor() {

            @Override
            public void visit(Projection r) throws VisitorException {              
            }

            @Override
            public void visit(Selection r) throws VisitorException {   
            }

            @Override
            public void visit(Table r) throws VisitorException {
            }

            @Override
            public void visit(VariableTable r) throws VisitorException {
            }

            @Override
            public void visit(Union r) throws VisitorException {
            }

            @Override
            public void visit(Intersection r) throws VisitorException {
            }

            @Override
            public void visit(Join r) throws VisitorException {
            }

            @Override
            public void visit(Product r) throws VisitorException {
               addFragment("F5", r);
            }
        };
        
        try {
            q.accept(v);
            qp = determineBestQueryPlan();
            
        } catch (VisitorException ex) {
        }
                
        return qp;
    }
    
    public QueryPlan determineBestQueryPlan(){
        QueryPlan bestPlan = null;
        int bestCost = 0;
        
        for(int i=0; i<5000; i++){
            QueryPlan qp = new QueryPlan();   
                 
            for(Map.Entry<String, Relation> entry : getFragments().entrySet()){  
                int alea = (int)Math.round(Math.random()*(getSites().size()-1));
                int j = 0;
                String site = "";

                Iterator<String> it = getSites().iterator();

                while(it.hasNext()){
                    String s = it.next();

                    if(alea == j){
                        site = s;
                    }

                    j++;
                }
                
                if(qp.containsKey(site)){
                    qp.get(site).add(entry.getValue());
                }
                else{
                    qp.put(site, entry.getValue());
                }
            }
            
            int cout = getCost(qp);
            
            if(bestPlan == null){
                bestPlan = qp;
                bestCost = cout;
            }
            else{
                if(cout<bestCost){
                    bestPlan = qp;
                    bestCost = cout;
                }
            }
        }
        
        return bestPlan;
    }

}